﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.IO;

namespace Compiler
{
    internal class AssemblyLoader:MarshalByRefObject
    {
        internal List<string> GetDefaultReferencedAssemblies()
        {
            Type codeTaskFactoryType = GetCodeTaskFactoryType();
            string[] defRefAsms = null;
            string fwVersion = CompletionData.ReservedProperties.GetInstance().GetProperty("MSBuildToolsVersion");
            if (fwVersion == "12.0")
            {
                FieldInfo fwInfo = GetFieldInfo(codeTaskFactoryType, "defaultReferencedFrameworkAssemblyNames", BindingFlags.Static | BindingFlags.NonPublic);
                string[] fwAsms = GetFieldValue<string[]>(codeTaskFactoryType, fwInfo);
                FieldInfo msbuildInfo = GetFieldInfo(codeTaskFactoryType, "defaultReferencedMSBuildAssemblyNames", BindingFlags.Static | BindingFlags.NonPublic);
                string[] msbuildAsms = GetFieldValue<string[]>(codeTaskFactoryType, msbuildInfo);
                defRefAsms = new string[fwAsms.Length + msbuildAsms.Length];
                Array.Copy(fwAsms, defRefAsms, fwAsms.Length);
                Array.Copy(msbuildAsms, 0, defRefAsms, fwAsms.Length, msbuildAsms.Length);
                
            }
            else
            {
                FieldInfo fieldInfo = GetFieldInfo(codeTaskFactoryType, "defaultReferencedAssemblies", BindingFlags.Instance | BindingFlags.NonPublic);
                defRefAsms = GetFieldValue<string[]>(codeTaskFactoryType, fieldInfo);
            }

            MethodInfo methodInfo = GetMethodInfo(codeTaskFactoryType, "AddReferenceAssemblyToReferenceList", BindingFlags.Instance | BindingFlags.NonPublic);
            object instance = Activator.CreateInstance(codeTaskFactoryType);
            List<string> result = new List<string>();
            foreach (string refAsm in defRefAsms)
                methodInfo.Invoke(instance, new object[] { result, refAsm });

            return result;
        }

        internal List<string> GetDefaultUsingNamespaces()
        {
            Type codeTasFactoryType = GetCodeTaskFactoryType();
            FieldInfo fieldInfo = GetFieldInfo(codeTasFactoryType, "defaultUsingNamespaces", BindingFlags.Instance | BindingFlags.NonPublic);
            string[] defUsingNss = GetFieldValue<string[]>(codeTasFactoryType, fieldInfo);
            return defUsingNss.ToList();
        }

        private Type GetCodeTaskFactoryType()
        {
            CompletionData.ReservedProperties propsInstance = CompletionData.ReservedProperties.GetInstance();
            string schemaDir = propsInstance.GetProperty("MSBuildToolsPath");
            if (schemaDir == null)
                return null;

            string fwVersion = propsInstance.GetProperty("MSBuildToolsVersion");
            if (fwVersion == null)
                return null;
            Assembly tasksAsm = Assembly.LoadFrom(Path.Combine(schemaDir, string.Format("Microsoft.Build.Tasks.v{0}.dll", fwVersion)));
            Type codeTaskFactoryType = tasksAsm.GetType("Microsoft.Build.Tasks.CodeTaskFactory");
            return codeTaskFactoryType;
        }

        private FieldInfo GetFieldInfo(Type targetType, string fieldName, BindingFlags bindingFlags)
        {
            if (targetType == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(fieldName))
                throw new ArgumentNullException();

            FieldInfo fieldInfo = targetType.GetField(fieldName, bindingFlags);
            return fieldInfo;
        }

        private MethodInfo GetMethodInfo(Type targetType, string methodName, BindingFlags bindingFlags)
        {
            if (targetType == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(methodName))
                throw new ArgumentNullException();

            MethodInfo methodInfo = targetType.GetMethod(methodName, bindingFlags);
            return methodInfo;

        }

        private T GetFieldValue<T>(Type targetType, FieldInfo fieldInfo)
        {
            if (targetType == null)
                throw new ArgumentNullException();
            if (fieldInfo == null)
                throw new ArgumentNullException();

            object instance = Activator.CreateInstance(targetType);
            object fieldValue = fieldInfo.GetValue(instance);
            return (T)fieldValue;
        }
    }
}
